﻿Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.ComponentModel.Design
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Windows.Forms.Design
Imports System.Workflow.ComponentModel

Namespace System.Workflow.ComponentModel.Design
    Public Class WorkflowOutline
        Inherits UserControl
        ' Events
        Protected Friend Custom Event Expanding As TreeViewCancelEventHandler
            AddHandler(ByVal value As TreeViewCancelEventHandler)
                AddHandler Me.treeView.BeforeExpand, value
            End AddHandler
            RemoveHandler(ByVal value As TreeViewCancelEventHandler)
                RemoveHandler Me.treeView.BeforeExpand, value
            End RemoveHandler
        End Event

        ' Methods
        Public Sub New(ByVal serviceProvider As IServiceProvider)
            Me.serviceProvider = serviceProvider
            If (serviceProvider Is Nothing) Then
                Throw New ArgumentNullException("serviceProvider")
            End If
            If Not TypeOf Me.GetService(GetType(IDesignerHost)) Is IDesignerHost Then
                Throw New InvalidOperationException(SR.GetString("General_MissingService", New Object() { GetType(IDesignerHost).FullName }))
            End If
            Dim surface As DesignSurface = [TryCast](Me.GetService(GetType(DesignSurface)),DesignSurface)
            If (surface Is Nothing) Then
                Throw New InvalidOperationException(SR.GetString("General_MissingService", New Object() { GetType(DesignSurface).FullName }))
            End If
            AddHandler surface.Loaded, New LoadedEventHandler(AddressOf Me.OnSurfaceLoaded)
            Dim service As IComponentChangeService = [TryCast](Me.GetService(GetType(IComponentChangeService)),IComponentChangeService)
            If (Not service Is Nothing) Then
                AddHandler service.ComponentChanged, New ComponentChangedEventHandler(AddressOf Me.OnComponentChanged)
                AddHandler service.ComponentRename, New ComponentRenameEventHandler(AddressOf Me.OnComponentRename)
            End If
            AddHandler WorkflowTheme.ThemeChanged, New EventHandler(AddressOf Me.OnThemeChanged)
            Dim service2 As ISelectionService = [TryCast](Me.GetService(GetType(ISelectionService)),ISelectionService)
            If (Not service2 Is Nothing) Then
                AddHandler service2.SelectionChanged, New EventHandler(AddressOf Me.OnSelectionChanged)
            End If
            Dim service3 As IUIService = [TryCast](Me.GetService(GetType(IUIService)),IUIService)
            If (Not service3 Is Nothing) Then
                Me.Font = DirectCast(service3.Styles.Item("DialogFont"), Font)
            End If
            Me.treeView = New TreeView
            Me.treeView.Dock = DockStyle.Fill
            Me.treeView.HideSelection = False
            AddHandler Me.treeView.AfterSelect, New TreeViewEventHandler(AddressOf Me.OnTreeViewAfterSelect)
            AddHandler Me.treeView.MouseDown, New MouseEventHandler(AddressOf Me.OnTreeViewMouseDown)
            Me.treeView.Font = Me.Font
            Me.treeView.ItemHeight = Math.Max(Me.treeView.ItemHeight, &H12)
            MyBase.Controls.Add(Me.treeView)
        End Sub

        Protected Overridable Function CreateNewNode(ByVal activity As Activity) As WorkflowOutlineNode
            Return New WorkflowOutlineNode(activity)
        End Function

        Protected Overrides Sub Dispose(ByVal disposing As Boolean)
            If disposing Then
                Me.IsDirty = False
                RemoveHandler WorkflowTheme.ThemeChanged, New EventHandler(AddressOf Me.OnThemeChanged)
                Dim service As IComponentChangeService = [TryCast](Me.GetService(GetType(IComponentChangeService)),IComponentChangeService)
                If (Not service Is Nothing) Then
                    RemoveHandler service.ComponentChanged, New ComponentChangedEventHandler(AddressOf Me.OnComponentChanged)
                    RemoveHandler service.ComponentRename, New ComponentRenameEventHandler(AddressOf Me.OnComponentRename)
                End If
                Dim service2 As ISelectionService = [TryCast](Me.GetService(GetType(ISelectionService)),ISelectionService)
                If (Not service2 Is Nothing) Then
                    RemoveHandler service2.SelectionChanged, New EventHandler(AddressOf Me.OnSelectionChanged)
                End If
                Dim surface As DesignSurface = [TryCast](Me.GetService(GetType(DesignSurface)),DesignSurface)
                If (Not surface Is Nothing) Then
                    RemoveHandler surface.Loaded, New LoadedEventHandler(AddressOf Me.OnSurfaceLoaded)
                End If
                Me.serviceProvider = Nothing
            End If
            MyBase.Dispose(disposing)
        End Sub

        Protected Function GetNode(ByVal activity As Activity) As WorkflowOutlineNode
            Return [TryCast](Me.activityToNodeMapping.Item(activity),WorkflowOutlineNode)
        End Function

        Protected Overrides Function GetService(ByVal serviceType As Type) As Object
            If (Not Me.serviceProvider Is Nothing) Then
                Return Me.serviceProvider.GetService(serviceType)
            End If
            Return MyBase.GetService(serviceType)
        End Function

        Private Sub InsertDocOutlineNode(ByVal parentNode As WorkflowOutlineNode, ByVal activity As Activity, ByVal childIndex As Integer, ByVal addNestedActivities As Boolean)
            If Not Me.activityToNodeMapping.Contains(activity) Then
                Dim nodeToUpdate As WorkflowOutlineNode = Me.CreateNewNode(activity)
                If (Not nodeToUpdate Is Nothing) Then
                    Me.RefreshNode(nodeToUpdate, False)
                    Me.activityToNodeMapping.Add(activity, nodeToUpdate)
                    If (addNestedActivities AndAlso TypeOf activity Is CompositeActivity) Then
                        Dim activity2 As Activity
                        For Each activity2 In DirectCast(activity, CompositeActivity).Activities
                            Me.InsertDocOutlineNode(nodeToUpdate, activity2, nodeToUpdate.Nodes.Count, addNestedActivities)
                        Next
                    End If
                    If (Not parentNode Is Nothing) Then
                        parentNode.Nodes.Insert(childIndex, nodeToUpdate)
                    Else
                        Me.treeView.Nodes.Add(nodeToUpdate)
                    End If
                    Me.OnNodeAdded(nodeToUpdate)
                End If
            End If
        End Sub

        Protected Overridable Sub OnBeginUpdate()
        End Sub

        Private Sub OnComponentChanged(ByVal sender As Object, ByVal e As ComponentChangedEventArgs)
            If TypeOf e.OldValue Is ActivityCollectionChangeEventArgs Then
                Me.IsDirty = True
            ElseIf ((Not e.Member Is Nothing) AndAlso TypeOf e.Component Is Activity) Then
                Dim nodeToUpdate As WorkflowOutlineNode = [TryCast](Me.activityToNodeMapping.Item(e.Component),WorkflowOutlineNode)
                If ((Not nodeToUpdate Is Nothing) AndAlso String.Equals(e.Member.Name, "Enabled", StringComparison.Ordinal)) Then
                    Me.RefreshNode(nodeToUpdate, True)
                End If
            End If
        End Sub

        Private Sub OnComponentRename(ByVal sender As Object, ByVal e As ComponentRenameEventArgs)
            If TypeOf e.Component Is Activity Then
                Dim node As WorkflowOutlineNode = [TryCast](Me.activityToNodeMapping.Item(e.Component),WorkflowOutlineNode)
                If (Not node Is Nothing) Then
                    node.OnActivityRename(e.NewName)
                End If
            End If
        End Sub

        Protected Overridable Sub OnEndUpdate()
        End Sub

        Private Sub OnIdle(ByVal sender As Object, ByVal e As EventArgs)
            If (Me.IsDirty AndAlso Me.treeView.Visible) Then
                Me.ReloadWorkflowOutline
            End If
        End Sub

        Protected Overrides Sub OnLoad(ByVal e As EventArgs)
            MyBase.OnLoad(e)
            Me.ReloadWorkflowOutline
        End Sub

        Protected Overridable Sub OnNodeAdded(ByVal node As WorkflowOutlineNode)
        End Sub

        Protected Overridable Sub OnNodeSelected(ByVal node As WorkflowOutlineNode)
            Dim service As ISelectionService = [TryCast](Me.GetService(GetType(ISelectionService)),ISelectionService)
            If (((Not service Is Nothing) AndAlso (Not node Is Nothing)) AndAlso (Not service.PrimarySelection Is node.Activity)) Then
                Dim view As WorkflowView = [TryCast](Me.GetService(GetType(WorkflowView)),WorkflowView)
                If (Not view Is Nothing) Then
                    view.EnsureVisible(node.Activity)
                End If
                service.SetSelectedComponents(New Object() { node.Activity }, SelectionTypes.Replace)
            End If
        End Sub

        Protected Overridable Sub OnRefreshNode(ByVal node As WorkflowOutlineNode)
            If (Not node Is Nothing) Then
                Dim activity As Activity = node.Activity
                If (Not activity Is Nothing) Then
                    Dim num As Integer = If((Not Me.treeView.ImageList Is Nothing), Me.treeView.ImageList.Images.IndexOfKey(activity.GetType.FullName), -1)
                    If (num = -1) Then
                        Dim designer As ActivityDesigner = ActivityDesigner.GetDesigner(activity)
                        If (Not designer Is Nothing) Then
                            Dim stockImage As Bitmap = [TryCast](designer.StockImage,Bitmap)
                            If (Not stockImage Is Nothing) Then
                                If (Me.treeView.ImageList Is Nothing) Then
                                    Me.treeView.ImageList = New ImageList
                                    Me.treeView.ImageList.ColorDepth = ColorDepth.Depth32Bit
                                End If
                                Me.treeView.ImageList.Images.Add(activity.GetType.FullName, stockImage)
                                num = (Me.treeView.ImageList.Images.Count - 1)
                            End If
                        End If
                    End If
                    node.ImageIndex = node.SelectedImageIndex = num
                    node.RefreshNode
                End If
            End If
        End Sub

        Private Sub OnSelectionChanged(ByVal sender As Object, ByVal e As EventArgs)
            Dim service As ISelectionService = [TryCast](Me.GetService(GetType(ISelectionService)),ISelectionService)
            If ((Not service Is Nothing) AndAlso (Not service.PrimarySelection Is Nothing)) Then
                Me.treeView.SelectedNode = [TryCast](Me.activityToNodeMapping.Item(service.PrimarySelection),WorkflowOutlineNode)
                If (Not Me.treeView.SelectedNode Is Nothing) Then
                    Me.treeView.SelectedNode.EnsureVisible
                End If
            End If
        End Sub

        Private Sub OnSurfaceLoaded(ByVal sender As Object, ByVal e As LoadedEventArgs)
            Me.ReloadWorkflowOutline
        End Sub

        Private Sub OnThemeChanged(ByVal sender As Object, ByVal e As EventArgs)
            If (Me.treeView.Nodes.Count > 0) Then
                Me.treeView.ImageList.Images.Clear
                Me.RefreshWorkflowOutline
            End If
        End Sub

        Private Sub OnTreeViewAfterSelect(ByVal sender As Object, ByVal e As TreeViewEventArgs)
            Dim node As WorkflowOutlineNode = [TryCast](e.Node,WorkflowOutlineNode)
            Me.OnNodeSelected(node)
        End Sub

        Private Sub OnTreeViewMouseDown(ByVal sender As Object, ByVal e As MouseEventArgs)
            If (Not Me.treeView.GetNodeAt(e.Location) Is Nothing) Then
                Me.treeView.SelectedNode = Me.treeView.GetNodeAt(e.Location)
            End If
        End Sub

        Protected Sub RefreshNode(ByVal nodeToUpdate As WorkflowOutlineNode, ByVal refreshChildNodes As Boolean)
            Me.treeView.BeginUpdate
            Dim queue As New Queue([Of] WorkflowOutlineNode)
            queue.Enqueue(nodeToUpdate)
            Do While (queue.Count > 0)
                Dim node As WorkflowOutlineNode = queue.Dequeue
                Me.OnRefreshNode(node)
                If refreshChildNodes Then
                    Dim node2 As TreeNode
                    For Each node2 In node.Nodes
                        Dim item As WorkflowOutlineNode = [TryCast](node2,WorkflowOutlineNode)
                        If ((Not item Is Nothing) AndAlso (Not item.Activity Is Nothing)) Then
                            queue.Enqueue(item)
                        End If
                    Next
                End If
                Me.treeView.EndUpdate
            Loop
        End Sub

        Public Sub RefreshWorkflowOutline()
            If (Me.treeView.Nodes.Count > 0) Then
                Me.RefreshNode([TryCast](Me.treeView.Nodes.Item(0),WorkflowOutlineNode), True)
            End If
        End Sub

        Public Sub ReloadWorkflowOutline()
            Me.OnBeginUpdate
            Me.treeView.BeginUpdate
            Try 
                Me.treeView.Nodes.Clear
                Me.activityToNodeMapping.Clear
                Dim safeRootDesigner As IRootDesigner = ActivityDesigner.GetSafeRootDesigner(Me.serviceProvider)
                If (((Not safeRootDesigner Is Nothing) AndAlso (Not safeRootDesigner.Component Is Nothing)) AndAlso TypeOf safeRootDesigner.Component Is Activity) Then
                    Me.InsertDocOutlineNode(Nothing, [TryCast](safeRootDesigner.Component,Activity), 0, True)
                End If
                If Me.NeedsExpandAll Then
                    Me.treeView.ExpandAll
                End If
            Finally
                Me.treeView.EndUpdate
            End Try
            Me.IsDirty = False
            Dim service As ISelectionService = [TryCast](Me.GetService(GetType(ISelectionService)),ISelectionService)
            If ((Not service Is Nothing) AndAlso (Not service.PrimarySelection Is Nothing)) Then
                Me.treeView.SelectedNode = [TryCast](Me.activityToNodeMapping.Item(service.PrimarySelection),WorkflowOutlineNode)
                If (Not Me.treeView.SelectedNode Is Nothing) Then
                    Me.treeView.SelectedNode.EnsureVisible
                End If
            End If
            Me.OnEndUpdate
        End Sub


        ' Properties
        Private Property IsDirty As Boolean
            Get
                Return Me.isDirty
            End Get
            Set(ByVal value As Boolean)
                If (Me.isDirty <> value) Then
                    Me.isDirty = value
                    If value Then
                        AddHandler Application.Idle, New EventHandler(AddressOf Me.OnIdle)
                    Else
                        RemoveHandler Application.Idle, New EventHandler(AddressOf Me.OnIdle)
                    End If
                End If
            End Set
        End Property

        Protected Friend Property NeedsExpandAll As Boolean
            Get
                Return Me.needsExpandAll
            End Get
            Set(ByVal value As Boolean)
                Me.needsExpandAll = value
            End Set
        End Property

        Protected Friend ReadOnly Property RootNode As TreeNode
            Get
                If (Me.treeView.Nodes.Count > 0) Then
                    Return Me.treeView.Nodes.Item(0)
                End If
                Return Nothing
            End Get
        End Property

        Protected Friend ReadOnly Property TreeView As TreeView
            Get
                Return Me.treeView
            End Get
        End Property


        ' Fields
        Private activityToNodeMapping As Hashtable = New Hashtable
        Private isDirty As Boolean
        Private needsExpandAll As Boolean = True
        Private serviceProvider As IServiceProvider
        Private treeView As TreeView
    End Class
End Namespace

